/** @file
  IKE Packet related operation.

  Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "IpSecDebug.h"
#include "Ikev2/Utility.h"

/**
  Allocate a buffer for the IKE_PACKET and intitalize its Header and payloadlist.

  @return The pointer of the IKE_PACKET.

**/
IKE_PACKET *
IkePacketAlloc (
  VOID
  )
{
  IKE_PACKET  *IkePacket;

  IkePacket = (IKE_PACKET *) AllocateZeroPool (sizeof (IKE_PACKET));
  if (IkePacket == NULL) {
    return NULL;
  }

  IkePacket->RefCount = 1;
  InitializeListHead (&IkePacket->PayloadList);

  IkePacket->Header = (IKE_HEADER *) AllocateZeroPool (sizeof (IKE_HEADER));
  if (IkePacket->Header == NULL) {
    FreePool (IkePacket);
    return NULL;
  }
  return IkePacket;
}

/**
  Free the IkePacket by the specified IKE_PACKET pointer.

  @param[in]  IkePacket  The pointer of the IKE_PACKET to be freed.

**/
VOID
IkePacketFree (
  IN IKE_PACKET *IkePacket
  )
{
  LIST_ENTRY  *Entry;
  IKE_PAYLOAD *IkePayload;

  if (IkePacket == NULL) {
    return;
  }
  //
  // Check if the Packet is referred by others.
  //
  if (--IkePacket->RefCount == 0) {
    //
    // Free IkePacket header
    //
    if (!IkePacket->IsHdrExt && IkePacket->Header != NULL) {
      FreePool (IkePacket->Header);
    }
    //
    // Free the PayloadsBuff
    //
    if (!IkePacket->IsPayloadsBufExt && IkePacket->PayloadsBuf != NULL) {
      FreePool (IkePacket->PayloadsBuf);
    }
    //
    // Iterate payloadlist and free all payloads
    //
    for (Entry = (IkePacket)->PayloadList.ForwardLink; Entry != &(IkePacket)->PayloadList;) {
      IkePayload  = IKE_PAYLOAD_BY_PACKET (Entry);
      Entry       = Entry->ForwardLink;

      IkePayloadFree (IkePayload);
    }

    FreePool (IkePacket);
  }
}

/**
  Callback funtion of NetbufFromExt()

  @param[in]  Arg  The data passed from the NetBufFromExe().

**/
VOID
EFIAPI
IkePacketNetbufFree (
  IN VOID  *Arg
  )
{
  //
  // TODO: add something if need.
  //
}

/**
  Copy the NetBuf into a IKE_PACKET sturcture.

  Create a IKE_PACKET and fill the received IKE header into the header of IKE_PACKET
  and copy the recieved packet without IKE HEADER to the PayloadBuf of IKE_PACKET.

  @param[in]  Netbuf      The pointer of the Netbuf which contains the whole received
                          IKE packet.

  @return The pointer of the IKE_PACKET which contains the received packet.

**/
IKE_PACKET *
IkePacketFromNetbuf (
  IN NET_BUF *Netbuf
  )
{
  IKE_PACKET  *IkePacket;

  IkePacket = NULL;
  if (Netbuf->TotalSize < sizeof (IKE_HEADER)) {
    goto Error;
  }

  IkePacket = IkePacketAlloc ();
  if (IkePacket == NULL) {
    return NULL;
  }
  //
  // Copy the IKE header from Netbuf to IkePacket->Hdr
  //
  NetbufCopy (Netbuf, 0, sizeof (IKE_HEADER), (UINT8 *) IkePacket->Header);
  //
  // Net order to host order
  //
  IkeHdrNetToHost (IkePacket->Header);
  if (IkePacket->Header->Length < Netbuf->TotalSize) {
    goto Error;
  }

  IkePacket->PayloadTotalSize = IkePacket->Header->Length - sizeof (IKE_HEADER);
  IkePacket->PayloadsBuf      = (UINT8 *) AllocateZeroPool (IkePacket->PayloadTotalSize);

  if (IkePacket->PayloadsBuf == NULL) {
    goto Error;
  }
  //
  // Copy the IKE packet without the header into the IkePacket->PayloadsBuf.
  //
  NetbufCopy (Netbuf, sizeof (IKE_HEADER), (UINT32) IkePacket->PayloadTotalSize, IkePacket->PayloadsBuf);
  return IkePacket;

Error:
  if (IkePacket != NULL) {
    IkePacketFree (IkePacket);
  }

  return NULL;
}

/**
  Convert the format from IKE_PACKET to NetBuf.

  @param[in]  SessionCommon  Pointer of related IKE_COMMON_SESSION
  @param[in]  IkePacket      Pointer of IKE_PACKET to be copy to NetBuf
  @param[in]  IkeType        The IKE type to pointer the packet is for which IKE
                             phase. Now it supports IKE_SA_TYPE, IKE_CHILDSA_TYPE,
                             IKE_INFO_TYPE.

  @return a pointer of Netbuff which contains the IKE_PACKE in network order.

**/
NET_BUF *
IkeNetbufFromPacket (
  IN UINT8               *SessionCommon,
  IN IKE_PACKET          *IkePacket,
  IN UINTN               IkeType
  )
{
  NET_BUF       *Netbuf;
  NET_FRAGMENT  *Fragments;
  UINTN         Index;
  UINTN         NumPayloads;
  LIST_ENTRY    *PacketEntry;
  LIST_ENTRY    *Entry;
  IKE_PAYLOAD   *IkePayload;
  EFI_STATUS    RetStatus;

  RetStatus = EFI_SUCCESS;

  if (!IkePacket->IsEncoded) {
    IkePacket->IsEncoded = TRUE;
    //
    // Convert Host order to Network order for IKE_PACKET header and payloads
    // Encryption payloads if needed
    //
    if (((IKEV2_SESSION_COMMON *) SessionCommon)->IkeVer == 2) {
      RetStatus = Ikev2EncodePacket ((IKEV2_SESSION_COMMON *) SessionCommon, IkePacket, IkeType);
      if (EFI_ERROR (RetStatus)) {
        return NULL;
      }

    } else {
      //
      // If IKEv1 support, check it here.
      //
      return NULL;
    }
  }

  NumPayloads = 0;
  //
  // Get the number of the payloads
  //
  NET_LIST_FOR_EACH (PacketEntry, &(IkePacket)->PayloadList) {

    NumPayloads++;
  }
  //
  // Allocate the Framgents according to the numbers of the IkePayload
  //
  Fragments = (NET_FRAGMENT *) AllocateZeroPool ((1 + NumPayloads) * sizeof (NET_FRAGMENT));
  if (Fragments == NULL) {
    return NULL;
  }

  Fragments[0].Bulk = (UINT8 *) IkePacket->Header;
  Fragments[0].Len  = sizeof (IKE_HEADER);
  Index             = 0;

  //
  // Set payloads to the Framgments.
  //
  NET_LIST_FOR_EACH (Entry, &(IkePacket)->PayloadList) {
    IkePayload = IKE_PAYLOAD_BY_PACKET (Entry);

    Fragments[Index + 1].Bulk = IkePayload->PayloadBuf;
    Fragments[Index + 1].Len  = (UINT32) IkePayload->PayloadSize;
    Index++;
  }

  Netbuf = NetbufFromExt (
             Fragments,
             (UINT32) (NumPayloads + 1),
             0,
             0,
             IkePacketNetbufFree,
             NULL
             );

  FreePool (Fragments);
  return Netbuf;
}

